package com.scheduling.schedule.algorithm.impl;

import com.scheduling.machine.entity.Machine;
import com.scheduling.order.entity.Order;
import com.scheduling.product.entity.Operation;
import com.scheduling.schedule.algorithm.ScheduleAlgorithm;
import com.scheduling.schedule.dto.ScheduleResult;
import com.scheduling.schedule.entity.ProductionTask;
import com.scheduling.schedule.entity.ScheduleDetail;
import com.scheduling.schedule.entity.Tuple2;
import com.scheduling.schedule.entity.WorkOrder;
import com.scheduling.schedule.enums.ScheduleAlgorithmType;
import com.scheduling.schedule.service.MachineLoadService;
import com.scheduling.schedule.service.OperationScheduleService;
import com.scheduling.schedule.service.ScheduleDataService;
import com.scheduling.schedule.service.WorkOrderGenerationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 默认FIFO排产算法
 * 按照先进先出（First In First Out）的顺序进行排产，最简单的排产策略
 * <p>
 * 完整流程：
 * 1. 输入：订单列表
 * 2. 第一阶段：订单按创建时间排序（FIFO策略）
 * 3. 第二阶段：
 * 订单 → 工单生成 （依据订单项生成工单）
 * 工单 → 任务（基于机器负荷的顺序分配）
 * 4. 输出：完整排产结果（ Task 集合）
 *
 * @author 开发团队
 */
@Slf4j
@Component("DEFAULT_FIFO")
@RequiredArgsConstructor
public class DefaultFifoAlgorithm implements ScheduleAlgorithm {

    private final WorkOrderGenerationService workOrderGenerationService;
    private final MachineLoadService machineLoadService;
    private final ScheduleDataService scheduleDataService;
    private final OperationScheduleService operationScheduleService;

    @Override
    public ScheduleResult schedule(List<Order> orders, LocalDateTime scheduleStartTime) {
        log.info("开始执行默认FIFO排产算法，订单数量: {}", orders.size());

        long startTime = System.currentTimeMillis();
        ScheduleResult result = new ScheduleResult();

        try {
            if (orders.isEmpty()) {
                throw new IllegalArgumentException("订单列表不能为空");
            }

            //FIFO排序策略
            log.info("FIFO订单排序");
            List<Order> sortedOrders = sortWithFifoStrategy(orders);

            //订单 -> 工单
            log.info("工单生成");
            List<WorkOrder> workOrders = generateWorkOrders(sortedOrders, scheduleStartTime);

            //工单 -> 任务
            log.info("任务生成");
            List<ProductionTask> productionTasks = generateProductionTasks(workOrders, scheduleStartTime);

            //任务 -> 任务细节
            List<ScheduleDetail> scheduleDetails = generateScheduleDetails(productionTasks);

            //统计信息
            LocalDateTime scheduleEndTime = calculateScheduleEndTime(productionTasks);
            BigDecimal totalUtilization = scheduleDataService.calculateTotalUtilization(productionTasks,
                    scheduleStartTime, scheduleEndTime);

            //封装结果
            result.setSuccess(true);
            result.setScheduleStartTime(scheduleStartTime);
            result.setScheduleEndTime(scheduleEndTime);
            result.setTotalUtilization(totalUtilization);
            result.setGeneratedWorkOrders(workOrders);
            result.setProductionTasks(productionTasks);
            result.setScheduleDetails(scheduleDetails);
            result.setAlgorithmType(getAlgorithmType());
            result.setAlgorithmDescription(getDescription());
            result.setTotalWorkOrders(workOrders.size());
            result.setTotalTasks(productionTasks.size());
            result.setTotalMachines(calculateMachineCount(productionTasks));
            result.setExecutionTimeMs(System.currentTimeMillis() - startTime);

            log.info("默认FIFO排产算法执行完成，工单: {}, 任务: {}, 设备利用率: {}%, 执行耗时: {}ms",
                    workOrders.size(), productionTasks.size(), totalUtilization, result.getExecutionTimeMs());

        } catch (Exception e) {
            log.error("默认FIFO排产算法执行失败", e);
            result.setSuccess(false);
            result.setErrorMessage("算法执行失败: " + e.getMessage());
        }

        return result;
    }

    /**
     * FIFO排序策略
     * <p>1. 按创建时间升序排序（先创建的先处理）</p>
     * <p>2. 创建时间相同时按订单ID排序</p>
     */
    private List<Order> sortWithFifoStrategy(List<Order> orders) {
        orders.sort((order1, order2) -> {
            // 1. 按创建时间升序排序（FIFO核心策略）
            int timeCompare = order1.getCreateTime().compareTo(order2.getCreateTime());
            if (timeCompare != 0) {
                return timeCompare;
            }

            // 2. 创建时间相同时，按订单ID排序（确保稳定性）
            return order1.getOrderId().compareTo(order2.getOrderId());
        });

        // 记录FIFO排序结果
        log.info("FIFO排序完成，前5个订单：");
        for (int i = 0; i < Math.min(5, orders.size()); i++) {
            Order order = orders.get(i);
            log.info("第{}位: 订单 {} (创建时间: {})", i + 1, order.getOrderId(), order.getCreateTime());
        }

        return orders;
    }

    @Override
    public List<WorkOrder> generateWorkOrders(List<Order> orders, LocalDateTime scheduleStartTime) {
        return workOrderGenerationService.generateDefaultWorkOrders(orders, scheduleStartTime);
    }

    @Override
    public List<ProductionTask> generateProductionTasks(List<WorkOrder> workOrders, LocalDateTime scheduleStartTime) {
        List<ProductionTask> allTasks = new ArrayList<>();
        AtomicInteger taskSequence = new AtomicInteger(1);

        //获取机器负载情况
        MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machineAvailableTime =
                getMachineAvailableTime(scheduleStartTime);

        //处理工单
        for (WorkOrder workOrder : workOrders) {
            try {
                List<ProductionTask> orderTasks = createTasksForWorkOrder(workOrder, machineAvailableTime,
                        taskSequence);
                allTasks.addAll(orderTasks);

                log.debug("工单 {} 生成了 {} 个任务", workOrder.getWorkOrderId(), orderTasks.size());
            } catch (Exception e) {
                log.error("为工单 {} 生成任务失败: {}", workOrder.getWorkOrderId(), e.getMessage());
            }
        }

        log.info("FIFO生产任务生成完成，总任务数: {}", allTasks.size());
        return allTasks;
    }

    /**
     * 依据工单生成生产任务（FIFO策略）
     */
    private List<ProductionTask> createTasksForWorkOrder(
            WorkOrder workOrder,
            MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machinesAvailableTime,
            AtomicInteger taskSequence
    ) {
        List<ProductionTask> tasks = new ArrayList<>();
        Integer productQuantity = workOrder.getQuantity();
        String productID = workOrder.getProductId();

        //找到工序
        List<Operation> operations = scheduleDataService.getOperationsByProductId(productID);
        //工序执行顺序排序（按依赖关系排序成能够顺序执行的顺序）
        operationScheduleService.sortOperations(operations);

        //遍历工序生成任务
        LocalDateTime predecessorOpEndTime = LocalDateTime.MIN;
        for (Operation operation : operations) {
            //处理前置工序
            if (operation.getPredecessorOps() != null) {
                String[] predecessorOps = operation.getPredecessorOps().split(",");
                for (String predecessorOpId : predecessorOps) {
                    Optional<ProductionTask> predTask = tasks.stream()
                            .filter(task -> Objects.equals(task.getOperationId(), predecessorOpId))
                            .findFirst();

                    if (predTask.isPresent()) {
                        LocalDateTime currentTime = predTask.get().getPlanEndTime();
                        if (currentTime.isAfter(predecessorOpEndTime)) {
                            predecessorOpEndTime = currentTime;
                        }
                    }
                }
            }

            //找到需要的机器类型
            String machineTypeId = operation.getMachineTypeId();
            //找到对应的机器集合
            List<Machine> machines = scheduleDataService.getMachinesByType(machineTypeId);

            //使用FIFO机器选择策略（按机器ID排序，选择第一个可用的）
            String selectedMachineId = null;
            Tuple2<LocalDateTime, LocalDateTime> selectedInterval = null;

            // 按机器ID排序以确保FIFO的稳定性
            machines.sort(Comparator.comparing(Machine::getMachineId));

            for (Machine machine : machines) {
                //计算任务的生产时间
                double machineCapacity = machine.getMaxCapacity().doubleValue();
                double hoursRequired = productQuantity / machineCapacity;
                double halfHourUnits = Math.ceil(hoursRequired * 2);
                double totalHours = halfHourUnits * 0.5;
                Duration duration = Duration.ofMinutes((long) (totalHours * 60));

                //找到当前机器的负载情况
                String machineId = machine.getMachineId();
                List<Tuple2<LocalDateTime, LocalDateTime>> freeTimes = machinesAvailableTime.get(machineId);
                freeTimes.sort(Comparator.comparing(Tuple2::getFirst));

                //遍历所有时间段进行插入，找到最早能排产的时间段
                for (Tuple2<LocalDateTime, LocalDateTime> freeTime : freeTimes) {
                    LocalDateTime freeStart = freeTime.getFirst();
                    LocalDateTime freeEnd = freeTime.getSecond();

                    //如果前置工序的结束时间在当前 free 时间段之后，则无法分配
                    if (freeEnd.isBefore(predecessorOpEndTime)) continue;

                    //如果空闲时间的开始在前置工序结束时间之前，时间段应是 [predOpEndTime, freeEnd]
                    if (freeStart.isBefore(predecessorOpEndTime)) {
                        freeStart = predecessorOpEndTime;
                    }

                    //计算free时间
                    Duration freeDuration;
                    if (freeEnd.equals(LocalDateTime.MAX)) {
                        freeDuration = Duration.ofSeconds(Long.MAX_VALUE);
                    } else {
                        freeDuration = Duration.between(freeStart, freeEnd);
                    }

                    //如果能插入
                    if (freeDuration.compareTo(duration) >= 0) {
                        LocalDateTime taskEnd = freeStart.plusMinutes(duration.toMinutes());
                        Tuple2<LocalDateTime, LocalDateTime> allocateInterval = new Tuple2<>(freeStart, taskEnd);

                        //FIFO策略：选择第一个找到的可用机器
                        selectedMachineId = machineId;
                        selectedInterval = allocateInterval;
                        break; // 找到第一个可用的机器就停止
                    }
                }

                if (selectedMachineId != null) {
                    break; // 已经找到机器，停止遍历其他机器
                }
            }

            //生成任务
            if (selectedMachineId != null && selectedInterval != null) {
                ProductionTask task = new ProductionTask();
                String timeStamp = String.valueOf(System.currentTimeMillis()).substring(7);
                Duration productDuration = Duration.between(selectedInterval.getFirst(), selectedInterval.getSecond());

                task.setTaskId("FIFO_" + timeStamp + String.format("%03d", taskSequence.getAndIncrement()));
                task.setWorkOrderId(workOrder.getWorkOrderId());
                task.setOperationId(operation.getOperationId());
                task.setMachineId(selectedMachineId);
                task.setQuantity(productQuantity);
                task.setStatus("已分配");
                task.setStandardTime((int) productDuration.toMinutes());
                task.setPlannedTime((int) productDuration.toMinutes());
                task.setPlanStartTime(selectedInterval.getFirst());
                task.setPlanEndTime(selectedInterval.getSecond());
                task.setCreateTime(LocalDateTime.now());
                task.setUpdateTime(LocalDateTime.now());

                tasks.add(task);

                log.info("FIFO任务分配 - 工单: {}, 工序: {}, 机器: {}, 时间: {} - {}",
                        task.getWorkOrderId(), task.getOperationId(), task.getMachineId(),
                        selectedInterval.getFirst(), selectedInterval.getSecond());

                //更新机器空闲时间表
                updateMachineAvailableTime(machinesAvailableTime, selectedMachineId, selectedInterval);
            } else {
                log.error("无法为工序 {} 找到合适的机器和时间段", operation.getOperationId());
            }
        }

        return tasks;
    }

    /**
     * 更新机器空闲时间表
     */
    private void updateMachineAvailableTime(
            MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> machinesAvailableTime,
            String machineId,
            Tuple2<LocalDateTime, LocalDateTime> usedInterval) {

        LocalDateTime taskStart = usedInterval.getFirst();
        LocalDateTime taskEnd = usedInterval.getSecond();

        List<Tuple2<LocalDateTime, LocalDateTime>> machineFreeTime = machinesAvailableTime.get(machineId);
        Iterator<Tuple2<LocalDateTime, LocalDateTime>> iterator = machineFreeTime.iterator();
        List<Tuple2<LocalDateTime, LocalDateTime>> newSlots = new ArrayList<>();

        while (iterator.hasNext()) {
            Tuple2<LocalDateTime, LocalDateTime> slot = iterator.next();
            LocalDateTime slotStart = slot.getFirst();
            LocalDateTime slotEnd = slot.getSecond();

            // 检查任务是否完全包含在空闲时间段内
            if (!taskStart.isBefore(slotStart) && !taskEnd.isAfter(slotEnd)) {
                iterator.remove();

                // 保留左侧空闲时间段（如果存在）
                if (slotStart.isBefore(taskStart)) {
                    newSlots.add(new Tuple2<>(slotStart, taskStart));
                }

                // 保留右侧空闲时间段（如果存在）
                if (taskEnd.isBefore(slotEnd)) {
                    newSlots.add(new Tuple2<>(taskEnd, slotEnd));
                }
                break;
            }
        }

        // 添加新空闲段并确保有序
        machineFreeTime.addAll(newSlots);
        machineFreeTime.sort(Comparator.comparing(Tuple2::getFirst));
    }

    /**
     * 获取机器空闲时间表
     */
    private MultiValueMap<String, Tuple2<LocalDateTime, LocalDateTime>> getMachineAvailableTime(LocalDateTime scheduleStartTime) {
        return machineLoadService.getCurrentMachineAvailablePeriod(scheduleStartTime);
    }

    private List<ScheduleDetail> generateScheduleDetails(List<ProductionTask> productionTasks) {
        List<ScheduleDetail> details = new ArrayList<>();
        AtomicInteger sequence = new AtomicInteger(1);
        String timeStamp = String.valueOf(System.currentTimeMillis()).substring(7);

        for (ProductionTask task : productionTasks) {
            ScheduleDetail detail = new ScheduleDetail();
            detail.setDetailId("FIFO_DETAIL_" + timeStamp + String.format("%03d", sequence.getAndIncrement()));
            detail.setTaskId(task.getTaskId());
            detail.setMachineId(task.getMachineId());
            detail.setScheduledStartTime(task.getPlanStartTime());
            detail.setScheduledEndTime(task.getPlanEndTime());
            detail.setSequenceNo(sequence.get());
            detail.setCreateTime(LocalDateTime.now());

            details.add(detail);
        }

        return details;
    }

    private int calculateMachineCount(List<ProductionTask> productionTasks) {
        return (int) productionTasks.stream()
                .map(ProductionTask::getMachineId)
                .filter(Objects::nonNull)
                .distinct()
                .count();
    }

    private LocalDateTime calculateScheduleEndTime(List<ProductionTask> productionTasks) {
        return productionTasks.stream()
                .map(ProductionTask::getPlanEndTime)
                .filter(Objects::nonNull)
                .max(LocalDateTime::compareTo)
                .orElse(LocalDateTime.now());
    }

    @Override
    public String getAlgorithmType() {
        return ScheduleAlgorithmType.DEFAULT_FIFO.getCode();
    }

    @Override
    public String getDescription() {
        return ScheduleAlgorithmType.DEFAULT_FIFO.getDescription();
    }
}